package com.swanclouds.redfish.internal;

import com.swanclouds.api.CloudProvider;
import com.swanclouds.api.RedfishClient;
import com.swanclouds.api.exceptions.ResponseException;
import com.swanclouds.core.transport.*;
import com.swanclouds.core.transport.internal.HttpExecutor;
import com.swanclouds.model.identity.AuthStore;
import com.swanclouds.model.identity.Token;
import com.swanclouds.redfish.common.Auth;
import com.swanclouds.redfish.common.utils.StringHepler;
import com.swanclouds.redfish.identity.RedfishAuth;
import com.swanclouds.redfish.identity.RedfishToken;
import com.swanclouds.redfish.shared.RootResourceConcrete;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.Map;

/**
 * Responsible for authenticating and re-authenticating sessions for V1 and V2
 * of the Identity API
 */
public class OSAuthenticator {

    private static final String TOKEN_INDICATOR = "Tokens";
    private static final Logger LOG = LoggerFactory.getLogger(OSAuthenticator.class);

    public static void reAuthenticate() {
        LOG.debug("Re-Authenticating session due to expired Token or invalid response");
        OSClientSession session = OSClientSession.getCurrent();
        Token token = ((OSClientSession.OSClientSessionV1) session).getToken();
        SessionInfo info = new SessionInfo(token.getEndpoint(), true, session.getProvider(),false);
        authenticateV1((RedfishAuth) token, info, session.getConfig(), false);
    }
    /**
     * Invokes authentication to obtain a valid V1 Token, throws an
     * UnsupportedOperationException for an V2 attempt.
     *
     * @param auth the authentication credentials
     * @param endpoint the identity endpoint
     * @param config the client configuration
     * @param provider the client provider type
     * @param loadVersion  the
     * @return the OSClient
     */
    @SuppressWarnings("rawtypes")
    public static RedfishClient invoke(AuthStore auth, String endpoint, Config config,
                                       CloudProvider provider, boolean loadVersion) {
        SessionInfo info = new SessionInfo(endpoint,  false, provider);
        return authenticateV1((RedfishAuth) auth, info, config, loadVersion);
    }

    /**
     * Invokes V1 authentication via an existing token
     *
     * @param auth the token authentication
     * @param endpoint the identity endpoint
     * @param config the client configuration
     * @param provider the client provider
     * @param loadVersion the
     * @return the OSClient
     */
    @SuppressWarnings("rawtypes")
    public static RedfishClient invoke(RedfishAuth auth, String endpoint, Config config, CloudProvider provider, boolean loadVersion) {
        SessionInfo info = new SessionInfo(endpoint,  false, provider, loadVersion);
        return authenticateV1(auth, info, config, loadVersion);
    }

    private static RedfishClient.OSClientV1 authenticateV1(RedfishAuth auth, SessionInfo info, Config config, boolean loadVersion) {

        if (auth != null && auth.getType() != null && auth.getType().equals(Auth.Type.TOKEN)) {
            Map<String, String> headers = new HashMap<>();
            RedfishToken redfishToken = new RedfishToken();
            redfishToken.setEndpoint(info.endpoint);
            return OSClientSession.OSClientSessionV1.createSession(redfishToken, null, config).headers(headers);
        }
        HttpResponse sharedResponse = null;
        if (loadVersion) {
            HttpRequest<RootResourceConcrete> sharedRequest = HttpRequest.builder(RootResourceConcrete.class)
                    .header(ClientConstants.HEADER_CONTENT_TYPE, ClientConstants.CONTENT_TYPE_JSON)
                    .endpoint(info.endpoint)
                    .method(HttpMethod.GET)
                    .path(ClientConstants.URI_SEP)
                    .config(config)
                    .build();
            sharedResponse = HttpExecutor.create().execute(sharedRequest);
        }

        HttpRequest<RedfishToken> request = HttpRequest.builder(RedfishToken.class)
                .header(ClientConstants.HEADER_CONTENT_TYPE, ClientConstants.CONTENT_TYPE_JSON)
                .endpoint(info.endpoint)
                .method(HttpMethod.POST)
                .path(ClientConstants.SESSIONSERVICE_SESSIONS)
                .config(config)
                .entity(auth)
                .build();

        HttpResponse response = HttpExecutor.create().execute(request);

        if (response.getStatus() >= 400) {
            try {
                ResponseException re = ResponseException.mapException(response);
                re.setRequestInfo(request);
                throw re;
            } finally {
                HttpEntityHandler.closeQuietly(response);
            }
        }
        RedfishToken token = response.getEntity(RedfishToken.class);
        token.setId(response.header(ClientConstants.HEADER_X_AUTH_TOKEN));
        token.setETag(response.header(ClientConstants.HEADER_ETAG));

        token.setEndpoint(request.getEndpoint());
        token.setLocation(response.header(ClientConstants.HEADER_LOCATION));
        if (sharedResponse != null && loadVersion) {
            token.setRootResource(sharedResponse.getEntity(RootResourceConcrete.class));
        }
        token.setCredentials(auth);

        if (auth != null && auth.getType() != null && auth.getType().equals(Auth.Type.CREDENTIALS)) {
            token = token.applyContext(info.endpoint, auth);
        }

        if (!info.reLinkToExistingSession) {
            token = token.applyContext(info.endpoint);
            OSClientSession.OSClientSessionV1 v1 = OSClientSession.OSClientSessionV1.createSession(token, info.provider, config);
            if (response.header(ClientConstants.HEADER_SET_COOKIE) != null) {
                String cookie = response.header(ClientConstants.HEADER_SET_COOKIE);
                v1.qsessionid = cookie.split(";")[0].substring(11);
                v1.reqId = StringHepler.substring("/",token.getLocation(), false);
            }
            return v1;
        }
//
        OSClientSession.OSClientSessionV1 current = (OSClientSession.OSClientSessionV1)OSClientSession.OSClientSessionV1.getCurrent();
        if (current != null) {
            current.token = token;
            current.reqId = StringHepler.substring("/", token.getLocation(),false);
        }

        return current;
    }

    private static boolean isEmpty(String str) {
        if (str != null && str.length() > 0)
            return false;
        return true;
    }

    private static class SessionInfo {
        String endpoint;
        boolean reLinkToExistingSession;
        CloudProvider provider;
        boolean loadVersion;

        SessionInfo(String endpoint, boolean reLinkToExistingSession, CloudProvider provider) {
            this.endpoint = endpoint;
            this.reLinkToExistingSession = reLinkToExistingSession;
            this.provider = provider;
        }

        SessionInfo(String endpoint, boolean reLinkToExistingSession, CloudProvider provider, boolean loadVersion) {
            this.endpoint = endpoint;
            this.reLinkToExistingSession = reLinkToExistingSession;
            this.provider = provider;
            this.loadVersion = loadVersion;
        }
    }
}
